/**
 * General definition for an element
 * 
 * @author remo.lemma@lu.unisi.ch and patrick.zulian@lu.unisi.ch
 */

package ch.unisi.inf.projects.java.uom.elements;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import ch.unisi.inf.projects.java.uom.Constants;
import ch.unisi.inf.projects.java.uom.ConstantArrays;
import ch.unisi.inf.projects.java.uom.Pair;

public abstract class Element {
	
	/** Name / Unique identifier for the Element */
	private String name;
	
	/** Set of encoded fields */
	private Map<String,String> fields;
	
	/** Represents the state of the category */
	private boolean modified;
	
	/** ArrayList containing updates, the updates are couples so the length is always even */
	private List<Pair<String,String>> updates;
	
	/**
	 * Constructs a new Element
	 * @param name the name of the Element
	 * @param fields the fields of the Element
	 * @param modified the state of the Element
	 */
	public Element (final String name, final Map<String,String> fields, final boolean modified)
	{
		this.name = name.toUpperCase();
		this.fields = fields;
		this.modified = modified;
		this.updates = new ArrayList<Pair<String,String>>();
	}
	
	/**
	 * Returns the name of the Element
	 * @return the name of the Element
	 */
	public String getName ()
	{
		return this.name;
	}

	/**
	 * Returns the fields of the Element
	 * @return the map of fields
	 */
	public Map<String,String> getFields () 
	{
		return this.fields;
	}

	/**
	 * Checks if the Element has been modified
	 * @return true if it has been modified, false otherwise
	 */
	public boolean isModified () 
	{
		return this.modified;
	}
	
	/**
	 * Sets the status of the Element
	 * @param modified true or false depending on the state
	 */
	public void setModified(boolean modified) 
	{
		this.modified = modified;
	}
	
	/**
	 * Returns the set of pair containing the updates to be done and clears the variable
	 * @return the set of pairs of updates to be done
	 */
	public List<Pair<String,String>> getAndClearUpdates ()
	{
		final List<Pair<String,String>> oldUpdates = new ArrayList<Pair<String,String>>(this.updates);
		this.updates = new ArrayList<Pair<String,String>>();
		this.modified = false;
		return oldUpdates;
	}

	/**
	 * Returns the list of pair containing the updates to be done
	 * @return the list of pairs of updates to be done
	 */
	public List<Pair<String,String>> getUpdates ()
	{
		return this.updates;
	}
	
	/**
	 * Adds a new field (already encoded)
	 * @param field the field to be added
	 * @param value the value of the field
	 */
	protected void addField (final String field, final String value)
	{
		if (field == null || fields.keySet().contains(field.toUpperCase()))
			return;
		fields.put(field.toUpperCase(), value);
		updates.add(new Pair<String, String>(null,field.toUpperCase()));
		setModified(true);
	}

	/**
	 * Sets the name of the element
	 * @param name the new name
	 */
	protected void setName (final String name)
	{
		this.name = name;
	}
	
	/**
	 * Converts the ItemClass to a string returning its name
	 * @return the name of the ItemClass
	 * @see java.lang.Object#toString()
	 */
	public String toString ()
	{
		return getName();
	}
	
	/**
	 * Sets the fields of the element
	 * @param fields the new map of fields
	 */
	public void setFields (final Map<String,String> fields)
	{
		this.setModified(true);
		for (final String field : fields.keySet()) {
			if (!this.fields.containsKey(field.toUpperCase()))
				updates.add(new Pair<String,String>(null,field.toUpperCase()));
		}
		for (final String field : this.fields.keySet()) {
			if (!fields.containsKey(field.toUpperCase()) && !ConstantArrays.ITEM_COMMON_FIELDS.contains(field) && !Constants.ITEM_PRIMARY_KEY.value().equals(field))
				updates.add(new Pair<String,String>(field.toUpperCase(),null));
		}	
		this.fields = fields;
	}
	
	/**
	 * Modifies the name or the value of a field
	 * @param field the field's name
	 * @param valueOrField the new field's name or the new field's value
	 */
	abstract void setField (final String field, final String valueOrField);
	
	/**
	 * Removes a fields
	 * @param field the field to be removed
	 */
	abstract void removeField (final String field);
	
	/**
	 * Saves an Element
	 * @throws Exception handles the needed exceptions
	 */
	abstract void save() throws Exception;
	
	/**
	 * Deletes an Element
	 * @throws Exception handles the needed exceptions
	 */
	abstract void delete() throws Exception;

}
